#include <windows.h>
#include <memory>
#include "engine.h"
#include "ui.h"

static std::unique_ptr<Engine> g_engine;
static UI g_ui;
static Layout g_layout;
static bool setStartMode=false;
static bool setGoalMode=false;
static HDC g_backDC=nullptr;
static HBITMAP g_backBmp=nullptr;
static int g_backW=0, g_backH=0;
static void updateTitle(HWND hWnd) {
    SetWindowTextW(hWnd, L"Find Road UI - Zengtudor");
}

static std::unique_ptr<Algorithm> makeAlgo(int id) {
    switch(id){
        case 1: return std::make_unique<BFS>();
        case 2: return std::make_unique<DFS>();
        case 3: return std::make_unique<Dijkstra>();
        case 4: return std::make_unique<AStar>();
        case 5: return std::make_unique<GBFS>();
        case 6: return std::make_unique<BiBFS>();
    }
    return std::make_unique<BFS>();
}

static void recreateBackbuffer(HWND hWnd) {
    RECT r; GetClientRect(hWnd, &r);
    int w = r.right - r.left;
    int h = r.bottom - r.top;
    HDC wndDC = GetDC(hWnd);
    if (!g_backDC) g_backDC = CreateCompatibleDC(wndDC);
    if (g_backBmp) { DeleteObject(g_backBmp); g_backBmp=nullptr; }
    g_backBmp = CreateCompatibleBitmap(wndDC, w, h);
    SelectObject(g_backDC, g_backBmp);
    g_backW = w; g_backH = h;
    ReleaseDC(hWnd, wndDC);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_CREATE:
            g_engine = std::make_unique<Engine>(30, 40);
            g_engine->setAlgoId(4);
            g_engine->setAlgorithm(makeAlgo(4));
            g_layout = g_ui.computeLayout(hWnd, *g_engine);
            SetTimer(hWnd, 1, 16, nullptr);
            g_engine->randomizeObstacles(0.25, GetTickCount());
            g_engine->resetAlgo();
            recreateBackbuffer(hWnd);
            return 0;
        case WM_SIZE:
            g_layout = g_ui.computeLayout(hWnd, *g_engine);
            InvalidateRect(hWnd, nullptr, TRUE);
            recreateBackbuffer(hWnd);
            return 0;
        case WM_LBUTTONDOWN: {
            POINT pt{(short)LOWORD(lParam),(short)HIWORD(lParam)};
            Pos p;
            if (g_ui.pointToCell(g_layout, pt, *g_engine, p)) {
                if (setStartMode) { g_engine->setStart(p); setStartMode=false; }
                else if (setGoalMode) { g_engine->setGoal(p); setGoalMode=false; }
                else g_engine->getGrid().set_block(p, true);
                g_engine->resetAlgo();
                InvalidateRect(hWnd, nullptr, TRUE);
            }
            return 0;
        }
        case WM_RBUTTONDOWN: {
            POINT pt{(short)LOWORD(lParam),(short)HIWORD(lParam)};
            Pos p;
            if (g_ui.pointToCell(g_layout, pt, *g_engine, p)) {
                g_engine->getGrid().set_block(p, false);
                g_engine->resetAlgo();
                InvalidateRect(hWnd, nullptr, TRUE);
            }
            return 0;
        }
        case WM_KEYDOWN: {
            int vk = (int)wParam;
            if (vk>=0x31 && vk<=0x36) { int id = vk-0x30; g_engine->setAlgoId(id); g_engine->setAlgorithm(makeAlgo(id)); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk==VK_SPACE) { g_engine->toggleRun(); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk=='N') { g_engine->stepOnce(); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk=='R') { g_engine->resetAlgo(); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk=='C') { g_engine->clearObstacles(); g_engine->resetAlgo(); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk=='M') { g_engine->randomizeObstacles(0.25, GetTickCount()); g_engine->resetAlgo(); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk=='D') { g_engine->setDiagonal(!g_engine->getGrid().diagonal); g_engine->resetAlgo(); InvalidateRect(hWnd,nullptr,TRUE); }
            else if (vk=='S') { setStartMode=true; setGoalMode=false; }
            else if (vk=='G') { setGoalMode=true; setStartMode=false; }
            return 0;
        }
        case WM_TIMER:
            InvalidateRect(hWnd, nullptr, TRUE);
            return 0;
        case WM_PAINT: {
            PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps);
            g_engine->tick();
            if (!g_backDC) recreateBackbuffer(hWnd);
            RECT r; GetClientRect(hWnd, &r);
            HBRUSH bg = (HBRUSH)(COLOR_WINDOW+1);
            FillRect(g_backDC, &r, bg);
            g_ui.draw(hWnd, g_backDC, *g_engine);
            BitBlt(hdc, 0, 0, g_backW, g_backH, g_backDC, 0, 0, SRCCOPY);
            EndPaint(hWnd, &ps);
            return 0;
        }
        case WM_ERASEBKGND:
            return 1;
        case WM_DESTROY:
            if (g_backBmp) { DeleteObject(g_backBmp); g_backBmp=nullptr; }
            if (g_backDC) { DeleteDC(g_backDC); g_backDC=nullptr; }
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow) {
    const wchar_t* clsName = L"FindRoadUIWndClass";
    WNDCLASSW wc{};
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInst;
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = clsName;
    RegisterClassW(&wc);
    HWND hWnd = CreateWindowW(clsName, L"Find Road UI", WS_OVERLAPPEDWINDOW,
                              CW_USEDEFAULT, CW_USEDEFAULT, 1200, 800,
                              nullptr, nullptr, hInst, nullptr);
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    updateTitle(hWnd);
    MSG msg{};
    while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
    return 0;
}

